package EP::Visualization::LineGraphic;

use strict;

use EP::Config;
use EP::Visualization::Graphics;

use PDL;
use PDL::NiceSlice;
use File::Temp qw /tempfile/;

use PDL::Graphics::PGPLOT;
use PDL::Graphics::PGPLOT::Window;

use PGPLOT;
use GD;

our @ISA = qw ( EP::Visualization::Graphics );

my @COLORS_RGB = (
		  [0,255,255],
		  [0,0,0],
		  [0,0,255],
		  [255,0,255],
		  [128,128,128],
		  [0,128,0],
		  [0,255,0],
		  [128,0,0],
		  [0,0,128],
		  [128,128,0],
		  [128,0,128],
		  [255,0,0],
		  [192,192,192],
		  [0,128,128],
		  [255,255,255],
		  [255,255,0]
		 );

my @COLORS_NAMES = qw ( AQUA BLACK BLUE FUCHSIA GRAY GREEN LIME MAROON
			NAVY OLIVE PURPLE RED SILVER TEAL WHITE YELLOW );

sub setData {
  my $self = shift;
  my %params = @_;

  die "No data supplied to LineGraphic::setData" if not defined $params{data};
  $self->{data}                  = $params{data};
  $self->{plot_height}           = $params{plot_height};
  $self->{cell_width}            = $params{cell_width};
  $self->{tmpdir}                = $params{tmpdir} if defined $params{tmpdir};
  $self->{tmpdir}                = $EP::Config::h->{TMP} if not defined $params{tmpdir};

  $self->{colors}                = $params{colors} if defined $params{colors};

  $self->{timeseries}            = $params{timeseries} if defined $params{timeseries};
  $self->{timeseries}            = 0 if not defined $params{timeseries};

  $self->{noannot}               = exists $params{noannot} ? $params{noannot} : 0;

  $self->{interactive}           = $params{interactive} if     defined $params{interactive};
  $self->{interactive}           = 0                    if not defined $params{interactive};
}

sub getData {
  my $self = shift;
  return $self->{data};
}

sub getType {
  return "lineplot";
}

sub setVerticalHookSet{
  my $self = shift;
  my $hook= shift;
  $self->{verticalHook} = $hook;
  return;
}

sub getVerticalHookSet{
  my $self = shift;
  return $self->{verticalHook};
}

sub setHorizontalHookSet{
  my $self = shift;
  my $hook= shift;
  $self->{horizontalHook} = $hook;
  return;
}

sub getHorizontalHookSet{
  my $self = shift;
  return $self->{horizontalHook};
}

sub getWidth {
  my $self = shift;
  my $data = $self->getData();

  my ( $cols, $rows ) = $data->dims();

  return $cols * $self->{cell_width};
}

sub getHeight {
  my $self = shift;
  return $self->{plot_height};
}

sub to_png {
  my $self = shift;
  my ( $png, $height, $width, $xposition, $yposition ) = @_;

  my $data = $self->getData();
  my ( $cols, $rows ) = $data->dims();

  my $lineplot_fh = $self->draw_png_lineplot ( $data, $width, $height, 1 );
  my $gd_lineplot = GD::Image->new ( $width, $height );

  my $min = $data->abs()->min();
  my $max = $data->abs()->max();

  $max = 1 if $min == $max;

  my $yscale = $height/(2*$max);
  my $yoff   = $height/2;

  my $alldatapos = all $data >= 0;

  $yscale = ( $height ) / ($max) if $alldatapos;
  $yoff = ($height)              if $alldatapos;

  my $white  = $gd_lineplot->colorResolve ( 255,255,255 );
  my $black  = $gd_lineplot->colorResolve ( 0, 0, 0 );
  my $ltgrey = $gd_lineplot->colorResolve ( 211, 211, 211 );

#  # y axis (in two halves)
#  $gd_lineplot->line ( 0, $yoff, 0, $yoff - $max * $yscale, $black );
#  $gd_lineplot->line ( 0, $yoff, 0, $yoff + $max * $yscale, $black );

#  # x axis
  $gd_lineplot->line ( 0, $yoff, $width, $yoff, $black );

#  # y ticks
  if ( !$self->{noannot} ) {
    for ( my $i = 0; $i < $max; $i += $max/5 ) {
      $gd_lineplot->dashedLine ( 0, $yoff + $i * $yscale,  $width, $yoff + $i * $yscale, $ltgrey );
      $gd_lineplot->dashedLine ( 0, $yoff - $i * $yscale, $width, $yoff - $i * $yscale, $ltgrey );
    }

    # x ticks
    for ( my $i = 0; $i <= $width; $i += $width/$cols ) {
      $gd_lineplot->dashedLine ( $i, $yoff - $height/2, $i, $yoff + $height/2, $ltgrey ) if !$alldatapos;
      $gd_lineplot->dashedLine ( $i, 0, $i, $height, $ltgrey ) if $alldatapos;
    }
  }

  my $l = new GD::Image ( $lineplot_fh );
  return if not $l;
  $l->transparent ( $l->colorResolve(255,255,255) );
  $gd_lineplot->copy ( $l, -1, -1, 0, 0, $l->getBounds() );

  if ( !$self->{noannot} ) {
    $black = $png->colorResolve ( 0, 0, 0);

    for ( my $i = 0; $i < $max; $i += $max/5 ) {
      $png->string ( gdSmallFont, $xposition - 25, $yposition + $yoff + $i * $yscale - 6, sprintf ( "%.1f" , -$i ), $black ) if $i != 0 and (!$alldatapos); # if $i != $max - $max/5;
      $png->string ( gdSmallFont, $xposition - 25, $yposition + $yoff - $i * $yscale - 6, sprintf ( "%.1f" , $i ), $black ) if $i != 0;
      $png->string ( gdSmallFont, $xposition - 25, $yposition + $yoff - $i * $yscale - 6, sprintf ( "%.1f" , $i ), $black ) if ($i == 0) and (!$alldatapos);
      $png->string ( gdSmallFont, $xposition - 25, $yposition + $yoff - $i * $yscale - 12, sprintf ( "%.1f" , $i ), $black ) if ($i == 0) and ($alldatapos);
    }

    for ( my $i = 0; $i < $width; $i += $width/$cols ) {
      my $t = $i / ( $width / $cols ) + 1;
      $png->string ( gdSmallFont, $xposition + $i + 0.5 * $self->{'cell_width'} - 3 * length ( "$t" ), $yposition + $height, $t, $black );
    }
  }

  $png->copy ( $gd_lineplot, $xposition, $yposition, 0, 0, $width, $height );
}

sub to_svg {
  my $self = shift;
  my ( $svg, $height, $width, $xposition, $yposition ) = @_;

  my $data = $self->getData();
  my ( $cols, $rows ) = $data->dims();

  my $gg = $svg->group ( transform => "translate($xposition,$yposition)" );
  my $png_filename = $self->draw_png_lineplot ( $data, $width, $height );

  $gg->image ( 'x'    => 0,
	       'y'    => 0,
	       width  => $width,
	       height => $height,
	       "xlink:href"  => "$EP::Config::h->{TMP}/$png_filename"
	     );

  my $max = $data->abs()->max();

  my $yscale = $height/(2*$max);
  my $g = $gg->group ( transform => "translate(0," . $height/2 . ")" ) if any $data < 0;
  $g = $gg->group ( transform => "translate(0," . ($height) . ")" ) if all $data >= 0;

  $yscale = ( $height ) / $max if all $data >= 0;

  # y axis (in two halves)
  $g->line ( x1 => 0, y1 => 0, x2 => 0, y2 => -$max * $yscale, stroke => 'red',    'stroke-width' => 1  );
  $g->line ( x1 => 0, y1 => 0, x2 => 0, y2 => +$max * $yscale, stroke => 'orange', 'stroke-width' => 1  ) if any $data < 0;

  # x axis
  $g->line ( x1 => -3, y1 => 0, x2 => $width, y2 => 0,     stroke => 'green',  'stroke-width' => 1 );

  # y ticks
  for ( my $i = 0; $i < $max; $i += $max/5 ) {
    if ( any $data < 0 ) {
      $g->line ( x1 => -3, y1 => $i * $yscale,  x2 => 3, y2 => $i * $yscale,   stroke => 'blue',  'stroke-width' => 1 );
      $g->text ( x => -18, 'y' =>  $i * $yscale + 5, "font-size" => "9" )->cdata ( sprintf ( "%.1f" , -$i ) ); 
    }
    next if $i == 0;

    $g->line ( x1 => -3, y1 => -$i * $yscale, x2 => 3, y2 => -$i * $yscale,  stroke => 'blue',  'stroke-width' => 1 );
    $g->text ( x => -18, 'y' => -$i * $yscale + 5, "font-size" => "9" )->cdata ( sprintf ( "%.1f" ,  $i ) );
  }

  if ( $self->{interactive} ) {
    my $h_hooks = $self->getHorizontalHookSet();
    my $h_hook_hooks = $h_hooks->getHookSet();
    my $h_hook_annot = $h_hooks->getAnnot();

    my @h_annot_names = keys %{$h_hook_annot->{$$h_hook_hooks[0]}->{full_annot}};
    foreach ( @$h_hook_hooks ) {
      my $annot= $h_hook_annot->{$_}->{full_annot};
      $svg->tag ( 'hhook', hook_id => "$_", annot => $annot->{$h_annot_names[0]} );
    }

    my $v_hooks = $self->getVerticalHookSet();
    my $v_hook_hooks = $v_hooks->getHookSet();
    my $v_hook_annot = $v_hooks->getAnnot();

    my @v_annot_names = keys %{$v_hook_annot->{$$v_hook_hooks[0]}->{full_annot}};
    foreach ( @$v_hook_hooks ) {
      my $annot = $v_hook_annot->{$_}->{full_annot};
      $svg->tag ( 'vhook', hook_id => "$_", annot => $annot->{$v_annot_names[0]} );
    }

    for ( my $i = 0; $i < $width; $i += $width/$cols) {
      $g->rectangle ( x=> $i, 'y' => -$max * $yscale, width => $width/$cols, height => $max * $yscale * 2,
		      style=>"fill:pink", opacity => 0.5, onclick=>"selectRange(evt)",onmouseover=>"contSelect(evt)", hook_id => $$h_hook_hooks[$i/($width/$cols)] );
    }
  }

  # x ticks
  for ( my $i = 0; $i <= $width; $i += $width/$cols) {
    $g->line ( x1 => $i, x2 => $i, y1 => -3 , y2 => 3 , stroke => 'blue', 'stroke-width' => 1 );
  }

  $g->text ( x => 5, 'y' => -$max * $yscale + 10, "font-size" => "9" )->cdata ( "Size: $rows members" );

  return $svg;
}

sub draw_png_lineplot {
  my $self = shift;
  my ( $data, $width, $height, $for_to_png ) = @_;

  my $png = new File::Temp ( DIR => "$self->{tmpdir}",
			     UNLINK => 0,
			     SUFFIX => '.png' );

  my $D;

  return "" if $data->dim(0) == 1;

  if ( $data->dim(1) > 5000 ) {
    warn "Want a lineplot of something bigger than 5000. Take a random sample of 5000 genes and plot that. Cheating!!!";

    my %r;
    $r{int ( rand ( $data->dim(1) ) )} = 1 while scalar keys %r <= 5000;
    my @s = keys %r;
    $D = $data(:, pdl @s);
  } else {
    $D = $data;
  }

  my $win = PDL::Graphics::PGPLOT::Window->new ( Dev=>"$png/png", Size => [ $width, $height, 3], Axis => -2, CharSize => 0 );
  my $min = -max ( abs ( $D) ) if any $data < 0;
  $min = 0 if all $data >= 0;

  my $max = max ( $D );
  $max = $min + 1 if $max == $min;

  $win->env ( 0, $D->dim(0), $min, $max, {Axis => -2, CharSize => 0} );# if all $data >= 0;

  my $colors = ['RED'];
  $colors = $self->{colors} if defined $self->{colors};

  if ( not $self->{timeseries} ) {
    my $ord = ( xvals ( long, 3 * $D->dim(0) ) + 2 ) / 3;
    my $p = $D->slice('*3')->flat->reshape ( 3 * $D->dim(0), $D->dim(1) )->copy;
    $p->slice('2:-1:3') .= -999999;
    $p->inplace->setbadtoval(-999999);
    $win->tline ( $ord, $p, { MISSING => -999999, COLOUR => $colors } );
  } else {
    my $x = zeroes ( $D )->xlinvals ( 1, $D->dim(0) );
    $win->tpoints ( $x - 0.5, $D->setbadtoval(-999999), { COLOUR => $colors, MISSING => -999999, SYMBOL => 1, PLOTLINE => $self->{timeseries} } );
  }

  $win->close ();

  warn "Generated lineplot into PNG - $png - ($width x $height)";
  return $png if $for_to_png;

  my ($png_filename) = $png->filename() =~ /$self->{tmpdir}\/(.*)/;
  return $png_filename;
}

sub _GD_lineplot {
  my $self = shift;
  my ( $data, $width, $height, $lineplot_only ) = @_;

  my ( $cols, $rows ) = $data->dims();

  my $img = new GD::Image ( $width, $height );

  my $max = $data->abs()->max();

  my $yscale = $height/(2*$max);
  my $yoff   = $height/2;

  $yscale = ( $height - 20 ) / ($max) if all $data >= 0;
  $yoff = ($height - 20)              if all $data >= 0;

  my $white  = $img->colorAllocate ( 255,255,255 );
  my $red    = $img->colorAllocate ( 255, 0, 0 );
  my $green  = $img->colorAllocate ( 0, 255, 0 );
  my $black  = $img->colorAllocate ( 0, 0, 0 );

  # lines
  foreach my $r ( 0 .. $rows - 1 ) {
    my $pl = new GD::Polyline;

    foreach my $c ( 0 .. $cols - 1) {
      my $x = $width/$cols * $c + $width/($cols*2);
      my $y = 0;
      $y = -$data->at($c,$r) * $yscale if not $data->slice('$c,$r')->isbad; ###???? BAD???? isbad????

      $pl->addPt ( $x, $y );
    }

    $pl->offset ( 0, $yoff );

    my $color = $img->colorResolve ( @{$COLORS_RGB[ $r % 16 ]} );
    die "Problem allocating " . $COLORS_NAMES[$r % 16] if not $color >= 0;

    warn "Row $r drawn in ", $COLORS_NAMES[$r % 16] if $color >= 0;
    $img->polydraw ( $pl, $color );
  }

  # finish here if only the lineplot is needed.
  return $img if $lineplot_only;

#  # y axis (in two halves)
  $img->line ( 0, $yoff, 0, $yoff - $max * $yscale, $red );
  $img->line ( 0, $yoff, 0, $yoff + $max * $yscale, $red );

#  # x axis
  $img->line ( -3, $yoff, $width, $yoff, $green );

#  # y ticks
  for ( my $i = 0; $i < $max; $i += $max/5 ) {
    $img->line ( -3, $yoff + $i * $yscale,  3, $yoff + $i * $yscale, $black );
#    $img->string ( gdSmallFont, 7, $yoff + $i * $yscale + 8, sprintf ( "%.1f" , -$i ), $black ) if $i != $max - $max/5;
    next if $i == 0;

    $img->line ( -3, $yoff - $i * $yscale, 3, $yoff - $i * $yscale, $black );
#    $img->string ( gdSmallFont, 7, $yoff - $i * $yscale + 8, sprintf ( "%.1f" , $i ), $black );
  }

  # x ticks
  for ( my $i = 0; $i <= $width; $i += $width/$cols ) {
    $img->line ( $i, $yoff - 3, $i, $yoff + 3, $black );
  }

#  $img->string ( gdSmallFont, 5, $yoff - $max * $yscale, "Size: $rows members", $black );

  return $img;
}

1;
